"""
Bootstrap snippet to embed into standalone scripts.
Contains minimal implementations of start/end/progress and plotting helpers.
"""
from pathlib import Path
import os
import sys
from typing import Optional, Dict, Any

from rich.console import Console
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn, TimeElapsedColumn, MofNCompleteColumn
from rich.panel import Panel
from rich.align import Align
from rich.table import Table
from rich import box

console = Console()
 

def set_chinese_font(font_family: str = "Heiti TC") -> None:
    try:
        import matplotlib.pyplot as plt
        plt.rcParams['font.sans-serif'] = [font_family]
        plt.rcParams['axes.unicode_minus'] = False
        console.log(f"字体配置已加载：{plt.rcParams.get('font.sans-serif')}")
    except Exception as e:
        console.log(f"设置中文字体时出错（内联）：{e}")


def detect_chinese_font(preferred: list[str] | None = None) -> str | None:
    """Return the name of the first available Chinese font from the preferred list, or None."""
    try:
        import matplotlib.font_manager as fm
        if preferred is None:
            preferred = [
                'Heiti TC', 'PingFang SC', 'STHeiti', 'Songti SC', 'Hiragino Sans GB',
                'Microsoft YaHei', 'SimHei', 'Noto Sans CJK SC', 'PingFang', 'Apple LiGothic'
            ]
        # Build a mapping of family names found on the system by inspecting font files
        family_names = set()
        for font_path in fm.findSystemFonts(fontpaths=None, fontext='ttf') + fm.findSystemFonts(fontpaths=None, fontext='otf'):
            try:
                fp = fm.FontProperties(fname=font_path)
                name = fp.get_name()
                if name:
                    family_names.add(name)
            except Exception:
                continue
        # Try preferred list first
        for name in preferred:
            if name in family_names:
                return name
        # Fallback: pick any family name that looks like a CJK font
        for candidate in family_names:
            low = candidate.lower()
            if any(key in low for key in ('hei', 'song', 'fang', 'noto', 'yahei', 'simhei', 'li')):
                return candidate
    except Exception as e:
        console.log(f"detect_chinese_font 出错：{e}")
    return None


def set_plot_style(style_cfg: Dict[str, Any] = None) -> None:
    import matplotlib
    import matplotlib.pyplot as plt
    if style_cfg is None:
        style_cfg = {'style': 'darkgrid', 'dpi': 300, 'figsize': [8,6], 'bbox_inches': 'tight', 'font_family':'Heiti TC'}
    # choose a usable Chinese font if available
    preferred = [style_cfg.get('font_family', 'Heiti TC')]
    detected = detect_chinese_font(preferred)
    font_family = detected or style_cfg.get('font_family', 'Heiti TC')
    # Prefer the detected/desired font as the default family to improve CJK rendering
    matplotlib.rcParams['font.family'] = font_family
    # Also keep it in the sans-serif fallback list for libraries that query that
    matplotlib.rcParams['font.sans-serif'] = [font_family]
    matplotlib.rcParams['axes.unicode_minus'] = False
    try:
        import seaborn as sns
        sns.set_style(style_cfg.get('style', 'darkgrid'))
    except Exception:
        matplotlib.style.use(style_cfg.get('style', 'default'))
    plt.rcParams['figure.dpi'] = style_cfg.get('dpi', 300)
    plt.rcParams['figure.figsize'] = style_cfg.get('figsize', [8,6])
    plt.rcParams['savefig.bbox'] = style_cfg.get('bbox_inches', 'tight')


def force_chinese_font(ax, font_family: str = 'Heiti TC'):
    """
    Force-apply a Chinese font to the axis texts. `font_family` may be a family name
    or a path to a font file. This sets FontProperties(fname=...) on text objects so
    matplotlib will use the specified font file when rendering/saving.
    """
    try:
        import matplotlib.font_manager as fm
        import matplotlib as mpl
        from matplotlib.font_manager import FontProperties

        # Determine font file path
        font_path = None
        # If looks like a file path, use it
        if isinstance(font_family, str) and (font_family.endswith('.ttf') or font_family.endswith('.otf') or '/' in font_family or '\\' in font_family):
            font_path = font_family
        else:
            try:
                prop = FontProperties(family=font_family)
                font_path = fm.findfont(prop)
            except Exception:
                font_path = None

        fp = FontProperties(fname=font_path) if font_path else None

        # apply to tick labels, axis labels, title, legend
        texts = []
        texts.extend(ax.get_xticklabels())
        texts.extend(ax.get_yticklabels())
        try:
            texts.append(ax.xaxis.label)
            texts.append(ax.yaxis.label)
            texts.append(ax.title)
        except Exception:
            pass

        for t in texts:
            try:
                if fp:
                    t.set_fontproperties(fp)
                else:
                    t.set_fontfamily(font_family)
            except Exception:
                pass

        legend = ax.get_legend()
        if legend:
            try:
                if fp:
                    # legend title
                    try:
                        legend.get_title().set_fontproperties(fp)
                    except Exception:
                        pass
                    for text in legend.get_texts():
                        try:
                            text.set_fontproperties(fp)
                        except Exception:
                            pass
                else:
                    for text in legend.get_texts():
                        try:
                            text.set_fontfamily(font_family)
                        except Exception:
                            pass
            except Exception:
                pass
    except Exception as e:
        console.log(f"force_chinese_font 内联出错：{e}")


def font_path_for_family(family_name: str) -> str | None:
    """Return a font file path that matplotlib would use for the given family name."""
    try:
        import matplotlib.font_manager as fm
        from matplotlib.font_manager import FontProperties
        prop = FontProperties(family=family_name)
        return fm.findfont(prop)
    except Exception:
        return None


def start(label: Optional[str] = None) -> float:
    import time
    label = f" {label}" if label else ""
    start_time_str = time.strftime('%Y-%m-%d %H:%M:%S')
    start_time = time.time()
    try:
        # include an emoji for visual cue
        panel = Panel(Align.center(f"🚀 开始{label}\n{start_time_str}", vertical="middle"), title="开始", border_style="green")
        console.print(panel)
    except Exception:
        console.print(start_time_str)
    return start_time


def end(start_time: float, label: Optional[str] = None) -> None:
    import time
    label = f" {label}" if label else ""
    end_time_str = time.strftime('%Y-%m-%d %H:%M:%S')
    duration = time.time() - start_time
    try:
        # format duration as H:M:S
        secs = int(duration)
        hours, rem = divmod(secs, 3600)
        mins, secs = divmod(rem, 60)
        hhmmss = f"{hours:02d}:{mins:02d}:{secs:02d}"
        human = []
        if hours:
            human.append(f"{hours}小时")
        if mins:
            human.append(f"{mins}分")
        human.append(f"{secs}秒")
        human_str = ''.join(human)
        panel = Panel(Align.center(f"✅ 结束{label}\n{end_time_str}\n耗时 {human_str}（{hhmmss}）", vertical="middle"), title="结束", border_style="bright_green")
        console.print(panel)
    except Exception:
        console.print(f"结束{label} {end_time_str}，耗时 {int(duration)} 秒")


def print_summary_table(rows: list[tuple], columns: list[str] | None = None, title: str = "运行摘要") -> None:
    """Print a simple three-line-like table (header, body, footer) using Rich.

    rows: list of tuples representing table rows
    columns: list of column names
    """
    try:
        table = Table(title=title, box=box.SIMPLE_HEAVY)
        if not columns:
            # infer number of columns
            if rows and len(rows) > 0:
                columns = [f"列{i+1}" for i in range(len(rows[0]))]
            else:
                columns = []
        for col in columns:
            table.add_column(col, style="cyan", no_wrap=True)
        def fmt(x):
            # format floats to 3 decimal places, leave others as str
            try:
                if isinstance(x, float):
                    return f"{x:.3f}"
                # numpy float types
                import numbers
                if isinstance(x, numbers.Real) and not isinstance(x, bool):
                    # convert to float then format
                    return f"{float(x):.3f}"
            except Exception:
                pass
            return str(x)

        for r in rows:
            table.add_row(*[fmt(x) for x in r])
        console.print(table)
    except Exception as e:
        console.log(f"打印摘要表出错：{e}")


def log_saved(path: str | Path | os.PathLike, label: Optional[str] = None) -> None:
    """Log a saved file with timestamp and size in a small Rich panel.

    path: file path (str or PathLike)
    label: optional human label (e.g., task name)
    """
    try:
        from datetime import datetime
        p = Path(path)
        ts = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        size = None
        if p.exists() and p.is_file():
            try:
                size_bytes = p.stat().st_size
                if size_bytes < 1024:
                    size = f"{size_bytes} B"
                elif size_bytes < 1024**2:
                    size = f"{size_bytes/1024:.1f} KB"
                else:
                    size = f"{size_bytes/1024**2:.2f} MB"
            except Exception:
                size = None

        label = f" {label}" if label else ""
        # choose emoji by extension
        suffix = p.suffix.lower()
        if suffix in ('.png', '.jpg', '.jpeg', '.gif'):
            emoji = '🖼️'
        else:
            emoji = '💾'

        body = f"{emoji} 已保存{label}\n路径: {p}\n时间: {ts}"
        if size:
            body += f"\n大小: {size}"

        panel = Panel(Align.left(body), title="已保存", border_style="blue")
        console.print(panel)
    except Exception as e:
        console.log(f"log_saved 出错：{e}")


def progress(*, total: int = 100, **kwargs) -> Progress:
    spinner_style = kwargs.pop('spinner_style', 'green')
    complete_style = kwargs.pop('complete_style', 'bright_green')
    description_template = kwargs.pop('description_template', '[bold]{task.description}')
    show_spinner = kwargs.pop('show_spinner', True)
    columns = []
    if show_spinner:
        columns.append(SpinnerColumn(style=spinner_style))
    columns.append(TextColumn(description_template))
    columns.append(BarColumn(bar_width=None, complete_style=complete_style))
    columns.append(MofNCompleteColumn())
    columns.append(TimeElapsedColumn())
    return Progress(*columns, **kwargs)


# --- joblib compatibility shim ---
try:
    from joblib import Parallel, delayed
    def _joblib_available():
        return True
except Exception:
    # provide a very small fallback that implements the same call signature
    def _joblib_available():
        return False
    def delayed(func, *dargs, **dkwargs):
        # return a callable that when called executes func
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)
        return wrapper
    class Parallel:
        def __init__(self, n_jobs=1, **kwargs):
            self.n_jobs = n_jobs
        def __call__(self, tasks):
            # tasks is an iterable of callables (results producers)
            res = []
            for t in tasks:
                try:
                    res.append(t())
                except Exception as e:
                    res.append(e)
            return res



# Auto-apply plot style when this snippet is executed as a script (embedded standalone)
# or when the environment variable GMT_TEMPLATE_AUTO_STYLE is set to '1'.
try:
    _should_auto = ( __name__ == '__main__' ) or (str(__import__('os').environ.get('GMT_TEMPLATE_AUTO_STYLE','0'))=='1')
except Exception:
    _should_auto = False

if _should_auto:
    try:
        set_plot_style()
    except Exception:
        pass

# ---- embedded standalone template starts here ----

"""
Minimal standalone template scaffold.
Replace 构建 allcsv 索引 with the desired title when generating.

This file intentionally contains only a small, well-documented
argparse + logging + main() scaffold. Example computation, plotting
and table generation have been removed to keep the generated scripts
lean and safe to run in environments where dependencies may be missing.
"""

from pathlib import Path
import os
import sys

# When running as standalone, assume project root is two levels up
ROOT = Path(__file__).resolve().parent.parent

try:
    get_logger
except NameError:
    try:
        from src.bootstrap_helpers import get_logger, load_config_from_file
    except Exception:
        def get_logger(name: str = "gmt-template", level: str = "INFO"):
            import logging
            logger = logging.getLogger(name)
            if not logger.handlers:
                logger.addHandler(logging.StreamHandler())
            logger.setLevel(level)
            return logger

        def load_config_from_file(path: str = "config.yaml"):
            return {}


def _build_argparse():
    import argparse
    parser = argparse.ArgumentParser(description='脚手架: 最小化的生成脚本模板')
    parser.add_argument('--dry-run', action='store_true', help='只准备但不执行写出步骤')
    parser.add_argument('--verbose', action='store_true', help='输出详细日志')
    parser.add_argument('--no-run', action='store_true', help='生成但不实际执行主要计算逻辑')
    parser.add_argument('--n-jobs', type=int, default=None, help='并行作业数，覆盖环境变量 N_JOBS')
    return parser


def main(argv=None):
    parser = _build_argparse()
    args = parser.parse_args(argv)

    # config precedence: config.yaml -> ENV vars -> CLI args
    try:
        cfg = load_config_from_file('config.yaml')
    except Exception:
        cfg = {}

    # logging
    log_level = 'DEBUG' if args.verbose else cfg.get('log_level', os.environ.get('LOG_LEVEL', 'INFO'))
    logger = get_logger('generated-script', level=log_level)

    TASK_LABEL = '构建 allcsv 索引'

    # Optional start hook from helpers
    _start = globals().get('start')
    start_time = None
    if callable(_start):
        try:
            start_time = _start(TASK_LABEL)
        except Exception:
            start_time = None

    # Ensure plotting style and Chinese font are initialized if helpers are available.
    # This is intentionally lightweight and tolerant: if the helpers are not present
    # (e.g., running in an environment without the project installed), these calls
    # will silently no-op thanks to the fallbacks provided by import_header.py.
    try:
        # Preferred, small wrapper that will set rcParams and try to pick a good CJK font
        _set_plot_style = globals().get('set_plot_style')
        if _set_plot_style and callable(_set_plot_style):
            try:
                _set_plot_style()
            except Exception:
                # swallow errors from style application
                pass
        else:
            # If set_plot_style isn't available, try to detect and set a font directly
            _detect_chinese_font = globals().get('detect_chinese_font')
            if _detect_chinese_font and callable(_detect_chinese_font):
                try:
                    f = _detect_chinese_font()
                    if f:
                        import matplotlib
                        matplotlib.rcParams['font.family'] = f
                        matplotlib.rcParams['font.sans-serif'] = [f]
                        matplotlib.rcParams['axes.unicode_minus'] = False
                except Exception:
                    pass
    except Exception:
        # never fail script generation or execution due to font setup
        pass

    if args.no_run:
        logger.info('no-run 模式，跳过主要计算')
        return

    # Implement CSV indexing: traverse allcsv/, summarize each CSV and write outputs
    from datetime import datetime
    import json
    import pandas as pd

    ALLCSV = ROOT / 'allcsv'
    OUTDIR = ROOT / 'outputs'
    OUTDIR.mkdir(parents=True, exist_ok=True)

    def try_read_csv(path: Path):
        # Try a few common encodings
        encs = ['utf-8', 'gbk', 'latin1']
        for e in encs:
            try:
                df = pd.read_csv(path, encoding=e)
                return df, e
            except Exception:
                continue
        # last resort, let pandas infer with errors='replace'
        try:
            df = pd.read_csv(path, encoding='utf-8', engine='python', error_bad_lines=False)
            return df, 'utf-8-fallback'
        except Exception:
            return None, None

    index_rows = []
    index_detail = {}

    csv_paths = sorted([p for p in ALLCSV.glob('**/*.csv') if p.is_file()])
    total = len(csv_paths)
    logger.info(f'发现 {total} 个 CSV 文件，开始索引...')

    for i, p in enumerate(csv_paths, 1):
        rel = p.relative_to(ROOT)
        logger.info(f'[{i}/{total}] 处理 {rel}')
        size = None
        try:
            size = p.stat().st_size
        except Exception:
            size = None

        df, enc = try_read_csv(p)
        if df is None:
            logger.warning(f'无法解析 CSV: {rel}（编码/格式问题）')
            index_rows.append({
                'path': str(rel), 'filename': p.name, 'rows': None, 'cols': None, 'size_bytes': size or '',
                'encoding': enc or '', 'fields': '', 'notes': '无法解析'
            })
            index_detail[str(rel)] = {'error': '无法解析'}
            continue

        rows, cols = df.shape

        # columns list
        fields = list(df.columns.astype(str))

        # missing per column
        missing = df.isna().sum().to_dict()

        # numeric columns stats
        num_stats = {}
        for col in df.select_dtypes(include=['number']).columns:
            ser = df[col].dropna().astype(float)
            if ser.size == 0:
                num_stats[col] = None
                continue
            desc = ser.describe()
            # capture count, mean, std, min, 25%, 50%, 75%, max
            num_stats[col] = {
                'count': int(desc.get('count', 0)),
                'mean': float(desc.get('mean', float('nan'))),
                'std': float(desc.get('std', float('nan'))),
                'min': float(desc.get('min', float('nan'))),
                '25%': float(desc.get('25%', float('nan'))),
                '50%': float(desc.get('50%', float('nan'))),
                '75%': float(desc.get('75%', float('nan'))),
                'max': float(desc.get('max', float('nan'))),
            }

        index_rows.append({
            'path': str(rel),
            'filename': p.name,
            'rows': int(rows),
            'cols': int(cols),
            'size_bytes': int(size) if size is not None else '',
            'encoding': enc or '',
            'fields': ';'.join(fields),
            'notes': ''
        })

        index_detail[str(rel)] = {
            'fields': fields,
            'missing_per_column': {k: int(v) for k, v in missing.items()},
            'numeric_stats': num_stats
        }

    # Write CSV summary (flatten fields as semicolon-separated)
    out_csv = OUTDIR / 'allcsv_index.csv'
    pd.DataFrame(index_rows).to_csv(out_csv, index=False)

    # Write JSON detail
    out_json = OUTDIR / 'allcsv_index.json'
    with open(out_json, 'w', encoding='utf-8') as f:
        json.dump(index_detail, f, ensure_ascii=False, indent=2)

    # Write Markdown summary table
    out_md = OUTDIR / 'allcsv_index.md'
    with open(out_md, 'w', encoding='utf-8') as f:
        f.write(f'# allcsv 索引（生成于 {datetime.now().isoformat()}）\n\n')
        f.write('| 文件路径 | 文件名 | 行数 | 列数 | 大小(bytes) | 编码 | 字段数 | 字段示例（前5） | 备注 |\n')
        f.write('|---|---:|---:|---:|---:|---:|---:|---|---|\n')
        for r in index_rows:
            fields_example = ','.join(r['fields'].split(';')[:5]) if r['fields'] else ''
            f.write(f"| {r['path']} | {r['filename']} | {r.get('rows','')} | {r.get('cols','')} | {r.get('size_bytes','')} | {r.get('encoding','')} | {len(r['fields'].split(';')) if r['fields'] else 0} | {fields_example} | {r.get('notes','')} |\n")

    logger.info(f'索引已写出: {out_csv}, {out_json}, {out_md}')

    # Optional end hook
    _end = globals().get('end')
    if callable(_end) and start_time is not None:
        try:
            _end(start_time, TASK_LABEL)
        except Exception:
            logger.debug('结束时回调失败')


if __name__ == '__main__':
    main()
